grammar Tarrh;

options {
    k=1;                                // LL(1) - do not use LL(*)
    language=Java;                      // target language is Java (= default)
    output=AST;                         // build an AST
}

tokens {
    SEMICOLON   =   ';'     ;
    LPAREN      =   '('     ;
    RPAREN      =   ')'     ;
	COMMA		= 	','		;
	CURLYL		=	'{'		;
	CURLYR		=	'}'		;
	HOOKL		=	'['		;
	HOOKR		=	']'		;
	DOT			=	'.'		;

    // operators
    BECOMES			=	'='	;
    PLUS			=	'+'	;
    MINUS			=   '-'	;
	MULTI			=   '*'	;
	DIVISION		=   '/'	;

	EQUALS 			=   '==';
	NOTEQUALS		=   '!=';
	GREATER			=   '>'	;
	LESS			=	'<'	;
	GREATEREQUALS	=   '>=';
	LESSEQUALS		=   '<=';

	AND				=	'&&';
	OR				=	'||';
	MODULO			=	'%'	;

    // keywords
	CLASS_DECL	=	'class'		;
    VAR_DECL         =   'var'       ;
	CONST_DECL		= 	'const'		;
	NEW_EXPR	=	'new'		;
	NEW_ARRAY_EXPR=	'new[]'		;
	VAR_ARRAY_DECL	=	'var[]' 	;

	IF			=   'if'		;
	ELSE		= 	'else'		;
	TRY			=	'try'		;
	CATCH		=	'catch'		;
	WHILE		=	'while'		;
	PRINT		=	'<<'		;
	READ		=	'>>'		;
	TRUE		=	'true'		;
	FALSE		=	'false'		;
	
	// reserved words while not in the language itself
	PROGRAM     =   'PROGRAM'   ;
	THROW = 'throw';
	INT_LITERAL	= 	'INT_LITERAL'	;
	STRING_LITERAL =	'STRING_LITERAL'	;
	FUNCTION_DECL		=	'def' 	;
	BOOL_LITERAL_TRUE	= 'BOOL_LITERAL_TRUE';
	BOOL_LITERAL_FALSE	= 'BOOL_LITERAL_FALSE';
	FUNCTION_CALL	=	'FUNCTION_CALL';
	MAIN_FUNCTION_DECL	=	'MAIN_FUNCTION_DECL';
	ARGUMENT_DECL='ARGUMENT_DECL';
	ARGUMENT_CALL='ARGUMENT_CALL';
	EXPR_STATEMENT = 'EXPR_STATEMENT';
	STATEMENT_EXPR = 'STATEMENT_EXPR';
	VARIABLE_REF = 'VARIABLE_REF';
	MEMBER_VAR_DECL = 'MEMBER_VAR_DECL';
	MEMBER_VAR_ARRAY_DECL = 'MEMBER_VAR_ARRAY_DECL';
	MEMBER_CONST_DECL = 'MEMBER_CONST_DECL';
}

@header {
       package nl.utwente.fmt.vertalerbouw.tarrh.parser;
}
 
@lexer::header {
       package nl.utwente.fmt.vertalerbouw.tarrh.parser;
}
 
@rulecatch { 
    catch (RecognitionException e) { 
        throw e; 
    } 
}

// Parser rules
program			
		: classes_or_functions main_function_decl EOF
			 ->  ^(PROGRAM classes_or_functions main_function_decl )
		;

classes_or_functions
		: ( class_decl | function_decl )*
		;

main_function_decl
		:	statements
			-> ^(MAIN_FUNCTION_DECL statements)
		;

class_decl
		: CLASS_DECL^ class_identifier CURLYL! functions_or_member_variables CURLYR! SEMICOLON!
		;

function_decl
		: FUNCTION_DECL^ class_identifier function_identifier LPAREN! arguments_decl RPAREN! CURLYL! statements CURLYR! SEMICOLON!
		;

functions_or_member_variables
		: ( function_decl functions_or_member_variables | member_variable_decl (SEMICOLON! functions_or_member_variables)? )?
		;

arguments_decl
		: (argument_decl (COMMA! argument_decl)*)?
		;

argument_decl
		: VAR_DECL class_identifier VARIABLE_IDENTIFIER
			-> ^(ARGUMENT_DECL class_identifier VARIABLE_IDENTIFIER)
		;

member_variable_decl
		: VAR_DECL class_identifier VARIABLE_IDENTIFIER
			-> ^(MEMBER_VAR_DECL class_identifier VARIABLE_IDENTIFIER)
		| VAR_ARRAY_DECL class_identifier VARIABLE_IDENTIFIER
			-> ^(MEMBER_VAR_ARRAY_DECL class_identifier VARIABLE_IDENTIFIER)
		| CONST_DECL class_identifier VARIABLE_IDENTIFIER BECOMES expr
			-> ^(MEMBER_CONST_DECL class_identifier VARIABLE_IDENTIFIER expr)
		;

statements
		: ( statement (SEMICOLON! statements)? )?
		;

statement
		:	expr
			-> ^(EXPR_STATEMENT expr)
		|	print_statement
		|	read_statement
		|	variable_decl
		| 	if_statement
		| 	while_statement
		| 	try_statement
		|	throw_statement
		;

variable_decl
		: VAR_DECL^ class_identifier VARIABLE_IDENTIFIER
		| VAR_ARRAY_DECL^ class_identifier VARIABLE_IDENTIFIER
		| CONST_DECL^ class_identifier VARIABLE_IDENTIFIER BECOMES! expr
		;

print_statement 
		: PRINT^ expr
		;

read_statement
	 	: READ^ VARIABLE_IDENTIFIER
		;

while_statement
		: WHILE^ LPAREN! expr1 RPAREN! CURLYL! statements CURLYR!
		;

if_statement
		: IF^ LPAREN! expr1 RPAREN! CURLYL! statements CURLYR! ELSE CURLYL! statements CURLYR!
		;

try_statement
		:TRY^ CURLYL! statements CURLYR! CATCH LPAREN! class_identifier VARIABLE_IDENTIFIER RPAREN! CURLYL! statements CURLYR!
		;
		
throw_statement
	 	: THROW^ expr1
		;

expr	
		: expr1 (BECOMES^ expr)?
		;

expr1 	: expr2 (OR^ expr2)*
        ;

expr2	: expr3	(AND^ expr3)*
		;

expr3
		: expr4 ((EQUALS^ | NOTEQUALS^ | GREATER^ | LESS^ | GREATEREQUALS^ | LESSEQUALS^) expr4 )*
		;

expr4
    	: expr5 ((PLUS^ | MINUS^) expr5 )*
    	;

expr5
		: expr6 ((MULTI^ | DIVISION^ | MODULO^) expr6 )*
		;

expr6  
		: operand (DOT^	function_identifier LPAREN! arguments_call RPAREN! | HOOKL^ expr HOOKR!)*
		;

operand
   		:   VARIABLE_IDENTIFIER
   		|	NEW_EXPR^ class_identifier
   		|	NEW_ARRAY_EXPR^ class_identifier HOOKL! expr HOOKR!
   		|   LPAREN! expr RPAREN!
   		|	CURLYL statements CURLYR
   			-> ^(STATEMENT_EXPR statements)
   		|	bool_literal
   		|	int_literal
   		|	string_literal
   		|	function_identifier LPAREN arguments_call RPAREN
   			-> ^(FUNCTION_CALL function_identifier arguments_call)
   		;

arguments_call
		: (argument_call (COMMA! argument_call)*)?
		;

argument_call
		: expr
			-> ^(ARGUMENT_CALL expr)
		;

// Parser rules: literals
bool_literal	
		: TRUE
			-> ^(BOOL_LITERAL_TRUE)
		| FALSE
			-> ^(BOOL_LITERAL_FALSE)
		;
int_literal
		:    (PLUS | MINUS)? NUMBER
   			-> ^(INT_LITERAL (MINUS)? NUMBER)
   		;
   		
string_literal
		:	STRING
		-> ^(STRING_LITERAL STRING)
		;

// Parser rules: identifiers
class_identifier
		:	CLASS_OR_FUNCTION_IDENTIFIER
		;

function_identifier
		:	CLASS_OR_FUNCTION_IDENTIFIER
		;

// Lexer rules
VARIABLE_IDENTIFIER
    	:   LOWER  (LETTER | DIGIT)*
    	;

CLASS_OR_FUNCTION_IDENTIFIER
		: UPPER (LETTER | DIGIT)*
		;

NUMBER
    	:   DIGIT+
    	;

STRING 	:  '\'' .* '\''
		;

COMMENT
    	:   '//' .* '\n' 
            { $channel=HIDDEN; }
    	;

WS
    	:   (' ' | '\t' | '\f' | '\r' | '\n')+
            { $channel=HIDDEN; }
    	;

fragment DIGIT  
		:   ('0'..'9') ;
fragment LOWER  
		:   ('a'..'z') ;
fragment UPPER  
		:   ('A'..'Z') ;
fragment LETTER 
		:   LOWER | UPPER ;
